/*
 * @Author: Leung
 * @Date: 2025-07-21 19:51:42
 * @LastEditTime: 2025-08-06 23:47:56
 * @FilePath: \Projecte:\STM32F103ZET6\Peripheral modules\EEPROM_AT24C02\I2C_Soft\User\i2c\bsp_i2c_gpio.c
 * @Description: 
 * 
 * Copyright (c) 2025 by ${git_name_email}, All Rights Reserved. 
 */
#include "./i2c/bsp_i2c_gpio.h"
#include "stm32f10x.h"


static void I2C_GPIO_Config(void);


/* 延迟函数，用于I2C总线位延迟 */
static void I2C_Delay(void)
{
	uint8_t i;
    /**
     * 下面的时间是通过逻辑分析仪测试得到的。
     * 工作条件：CPU主频72MHz ，MDK编译环境，1级优化
     * 循环次数为10时，SCL频率 = 205KHz
     * 循环次数为7时，SCL频率 = 347KHz， SCL高电平时间1.5us，SCL低电平时间2.87us
     * 循环次数为5时，SCL频率 = 421KHz， SCL高电平时间1.25us，SCL低电平时间2.375us 
     */
	for (i = 0; i < 10; i++);
}


// 起始信号
void i2c_Start(void)
{
    /*当SCL高电平时，SDA出现一个下调沿表示起始信号*/
    SDA_1();
    SCL_1();
    I2C_Delay();    // 延时确保稳定
    SDA_0();        // SDA拉低
    I2C_Delay();
    SCL_0();        // 拉低SCL，为发送数据做准备，钳住总线
    I2C_Delay();
}

// 停止信号
void i2c_Stop(void)
{
    /*当SCL高电平时，SDA出现一个上升沿表示停止信号*/
    SDA_0();
    SCL_1();
    I2C_Delay();    // 延时确保稳定
    SDA_1();        // SDA拉高
}                  

// 发送一个字节
void i2c_SendByte(uint8_t _ucByte)
{
    uint8_t i;
    /* 先发送字节的高位bit7 */
    for (i = 0; i < 8; i++)
    {
         // 检查最高位是1还是0
        if (_ucByte & 0x80)
        {
            SDA_1();
        }
        else
        {
            SDA_0();
        }
        I2C_Delay();
        SCL_1();
        I2C_Delay();
        SCL_0();
        if(i == 7)
        {
            SDA_1();    // 释放总线
        }
        _ucByte <<= 1;  // 左移一个bit
        I2C_Delay();
    }
    
}

// 读取一个字节，CPU从I2C总线设备读取8bit数据
uint8_t i2c_ReadByte(void)
{
    uint8_t i;
    uint8_t value;

    /* 读到第1个bit为数据的bit7 */
    value = 0;
    for (i = 0; i < 8; i++)
    {
        value <<= 1;
        SCL_1();        // 拉高SCL，让从机把数据放到SDA上
        I2C_Delay();
        if(SDA_READ())
        {
            value++;
        }
        SCL_0();
        I2C_Delay();
    }
    return value;
}

// 等待应答信号
uint8_t i2c_WaitAck(void)
{
    uint8_t re;
    SDA_1();
    I2C_Delay();
    SCL_1();
    if(SDA_READ())
    {
        re = 1;
    }
    else
    {
        re = 0;
    }
    SCL_0();
    I2C_Delay();
    return re;
}


// 发送应答
void i2c_Ack(void)
{
    SDA_0();        // CPU驱动SDA = 0
    I2C_Delay();
    SCL_1();        // CPU产生1个时钟
    I2C_Delay();
    SCL_0();
    I2C_Delay();
    SDA_1();        // CPU释放SDA总线
}

// 发送非应答
void i2c_NAck(void)
{
    SDA_1();        // CPU驱动SDA = 1
    I2C_Delay();
    SCL_1();        // CPU产生1个时钟
    I2C_Delay();
    SCL_0();
    I2C_Delay();
}


/**
 * I2C_GPIO_Config初始化GPIO配置
 * 配置I2C总线的GPIO，采用模拟IO的方式实现
 * 因为需要主动控制SDA和SCL引脚，所以需要将这两个引脚配置为推挽输出模式
 */
void I2C_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(EEPROM_I2C_GPIO_CLK, ENABLE);

    GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
    // 通用开漏输出模式，只负责【主动拉低】，而高电平则交给【外部上拉电阻】去实现高阻态
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(EEPROM_I2C_GPIO_PORT, &GPIO_InitStructure);

    /* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
    i2c_Stop();
}


/**
 * 函 数 名: i2c_CheckDevice 监测设备地址
 * 功能说明: 检测I2C总线设备，CPU向发送设备地址，然后读取设备应答来判断该设备是否存在
 * 形    参：_Address：设备的I2C总线地址
 * 返 回 值: 返回值 0 表示正确， 返回1表示未探测到
 */
uint8_t i2c_CheckDevice(uint8_t _Address)
{
    uint8_t ucAck;
    I2C_GPIO_Config();      // 配置GPIO
    i2c_Start();            // 发送起始信号

    /* 发送设备地址+读写控制bit（0 = w， 1 = r) bit7 先传 */
	i2c_SendByte(_Address | EEPROM_I2C_WR);
	ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

    i2c_Stop();             // 发送停止信号

    return ucAck;
}


